﻿using System;
using System.Windows;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace System.Net.Sockets
{
    public class SimpleSocket
    {
        #region Constants
        /// <summary>
        /// The size of the buffers used by the connection
        /// </summary>
        const int BufferSize = 2048;
        #endregion

        #region Properties
        /// <summary>
        /// The base TCP socket
        /// </summary>
        protected Socket Client;

        /// <summary>
        /// Get the hostname of the connected server
        /// </summary>
        public string Hostname { get; private set; }

        /// <summary>
        /// Get the port of the connected server
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// The socket async event manager
        /// </summary>
        protected SocketAsyncEventArgs Manager = new SocketAsyncEventArgs();

        /// <summary>
        /// Get the active socket
        /// </summary>
        public Socket Socket
        {
            get
            {
                return this.Manager.ConnectSocket;
            }
        }
        #endregion

        #region Helpers
        /// <summary>
        /// Create a new socket message args
        /// </summary>
        /// <returns></returns>
        private SocketAsyncEventArgs CreateHandler()
        {
            return new SocketAsyncEventArgs { RemoteEndPoint = new DnsEndPoint(this.Hostname, this.Port) };
        }
        #endregion

        #region Events

        #region ConnectionClosed
        /// <summary>
        /// The handler for the ConnectionClosed event
        /// </summary>
        public delegate void ConnectionClosedHandler();

        /// <summary>
        /// The connection closed event raised if the connection can not be made or is forcebly closed
        /// </summary>
        public event ConnectionClosedHandler ConnectionClosed;

        /// <summary>
        /// Calls the ConnectionClosed event if it is set
        /// </summary>
        private void CallConnectionClosed()
        {
            if (this.ConnectionClosed != null)
                this.ConnectionClosed();
        }
        #endregion

        #region MessageReceived
        /// <summary>
        /// The handler for the MessageReceived event
        /// </summary>
        /// <param name="message"></param>
        public delegate void MessageReceivedHandler(string message);

        /// <summary>
        /// The message received event raised when data is retrieved from the server
        /// </summary>
        public event MessageReceivedHandler MessageReceived;
        #endregion

        #endregion

        /// <summary>
        /// Create a new WebSocket object
        /// </summary>
        /// <param name="hostname">The hostname of the server you are connecting to</param>
        /// <param name="port">The port where the server is litening on</param>
        public SimpleSocket(string hostname, int port)
        {
            this.Hostname = hostname;
            this.Port = port;

            this.Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Connect to the server at the given settings
        /// </summary>
        public void Connect()
        {
            //Create the connection config
            this.Manager = this.CreateHandler();
            this.Manager.Completed += (object sender, SocketAsyncEventArgs args) =>
            {
                if (args.SocketError == SocketError.Success && this.Client.Connected)
                {
                    //Connection successful
                    this.ReceiveNextMessage();
                }
                else
                {
                    //Connection failed
                    this.CallConnectionClosed();
                }
            };

            this.Client.ConnectAsync(Manager);
        }

        /// <summary>
        /// Set up a listener and wait for a response
        /// </summary>
        public void ReceiveNextMessage()
        {
            SocketAsyncEventArgs responseListener = this.CreateHandler();
            //Message received
            responseListener.Completed += (object sender, SocketAsyncEventArgs args) =>
            {
                //Call the disconected event if the socket is not connected anymore
                if (!this.Socket.Connected)
                {
                    this.CallConnectionClosed();
                    return;
                }

                var response = Encoding.UTF8.GetString(args.Buffer, 0, args.BytesTransferred);

                //Ignore empty messages
                if (!string.IsNullOrWhiteSpace(response) && this.MessageReceived != null)
                {
                    this.MessageReceived(response);
                }

                //TODO: read the next message if the buffer is full

                this.ReceiveNextMessage();
            };

            //Prepare a buffer and send the request
            byte[] buffer = new byte[2048];
            responseListener.SetBuffer(buffer, 0, buffer.Length);
            this.Socket.ReceiveAsync(responseListener);
        }

        /// <summary>
        /// Send the string to the connection
        /// </summary>
        /// <param name="data"></param>
        public void Send(string data)
        {
            this.Send(System.Text.Encoding.UTF8.GetBytes(data));
        }

        /// <summary>
        /// Send the data to the connection
        /// </summary>
        /// <param name="data"></param>
        public void Send(byte[] data)
        {
            if (!this.Socket.Connected)
                this.CallConnectionClosed();

            SocketAsyncEventArgs message = this.CreateHandler();
            message.SetBuffer(data, 0, data.Length);
            this.Socket.SendAsync(message);
        }
    }
}
